具体类型
可以设计出许多用户定义类型去满足各种各样的需求。现在来考虑一个遵循 complex 那种定义方式的用户定义 Stack 类型。为了使这个例子更实际一点,这个 Stack 类型的定义以它的元素个数作为一个参数:
class Stack
{
char* v;
int top;
int max_size;
public:
class Underflow{}; // 用做异常
class Overflow{}; // 用做异常
class Bad_size{}; // 用做异常
Stack(int s); // 构造函数
~Stack(); // 析构函数
void push(char c);
char pop();
};
构造函数 Stack(int)
将在建立这个类的对象时被调用,它处理初始化问题。如果该类的一个对象出了其作用域,需要做某些清理时,就应该去声明构造函数的对应物---它被称为析构函数:
Stack::Stack(int s) // 构造函数
{
top = 0;
if(s < 0 || 10000 < s) throw Bad_size(); // "||"的意思是“或”
max_size = s;
v = new char[s]; // 在自由存储(堆,动态存储)中为元素分配存储
}
Stack::~Stack() // 析构函数
{
delete [] v; // 释放元素存储,使空间可能重新使用
}
构造函数初始化新的 Stack 变量,在做这件事时,它用 new
运算符从自由空间(也称为堆或动态存储)分配一些存储。析构函数进行清理,方式就是释放这些存储。所有这些都能在无需 Stack 的用户干预的情况下完成。有关用户只要简单地建立和使用 Stack,就像它们是内部类型的变量似的。例如,
Stack s_var1(10); // 具有10个元素的全局堆栈
void f(Stack& s_ref, int i) // 对Stack的引用
{
Stack s_var2(i); // 具有i个元素的局部堆栈
Stack* s_ptr = new Stack(20); // 指向在自由存储分配的Stack
s_var1.push('a');
s_var2.push('b');
s_ref.push('c');
s_ptr->push('d');
// ...
}
这个Stack类型遵循与内部类型(例如 int
和 char
等)同样的规则,包括命名、作用域、存储分配、生存时间、复制等。
当然,成员函数 push()
和 pop()
也必须在某个地方给予定义:
void Stack::push(char c)
{
if(top == max_size) throw Overflow();
v[top] = c;
top = top + 1;
}
char Stack::pop()
{
if(top == 0) throw Underflow();
top = top - 1;
return v[top];
}
像 complex 和 Stack 这样的类型称为具体类型,它们与抽象类型相对应。抽象类型的界面能更完全地将用户与实现细节隔离。
🔚